package com.fruit.logistics.sf.service;

import com.fruit.logistics.LogisticsProxyService;
import com.fruit.logistics.sf.pojo.SFRequest;
import com.fruit.logistics.sf.pojo.SFResponse;
import com.fruit.logistics.sf.pojo.deliver.*;
import com.fruit.logistics.sf.pojo.newdeliver.*;
import com.fruit.logistics.sf.pojo.order.*;
import com.fruit.logistics.sf.pojo.route.*;
import com.fruit.pojo.logistics.*;
import com.fruit.service.logistics.LogisticsService;
import com.fruit.service.logistics.NoSuitableProxyException;
import com.fruit.service.logistics.RouteListener;
import com.joe.http.IHttpClientUtil;
import com.joe.http.request.IHttpRequestBase;
import com.joe.utils.common.BeanUtils;
import com.joe.utils.parse.xml.XmlParser;
import com.joe.utils.secure.IBase64;
import com.joe.utils.secure.MD5;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URLEncoder;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 顺丰的快递代理
 *
 * @author joe
 * @version 2018.02.06 15:17
 */
@Slf4j
@Service
public class SFLogisticsProxyService implements LogisticsProxyService {
    private static final XmlParser PARSER = XmlParser.getInstance();
    private static final IHttpClientUtil CLIENT = new IHttpClientUtil();
    private static final MD5 MD5 = new MD5();
    private static final IBase64 BASE64 = new IBase64();
    /**
     * 顺丰Checkword
     */
    @Value("${com.fruit.logistics.sf.checkword}")
    private String checkword;
    /**
     * 接入编码
     */
    @Value("${com.fruit.logistics.sf.code}")
    private String code;
    /**
     * 下单URL
     */
    @Value("${com.fruit.logistics.sf.url}")
    private String url;
    /**
     * 是否是沙箱模式，沙箱模式下下单是模拟下单，true表示当前是沙箱模式
     */
    @Value("${com.fruit.logistics.sf.sandbox}")
    private boolean sandbox = false;
    /**
     * 实现了{@link com.fruit.service.logistics.LogisticsService#register(RouteListener)}的服务
     */
    private LogisticsService service;

    @Override
    public void init(LogisticsService service) {
        this.service = service;
    }

    @Override
    public boolean filter(LogisticsType logistics) {
        return LogisticsType.SF.equals(logistics);
    }

    @Override
    public void register(RouteListener listener) {
        service.register(listener);
    }

    @Override
    public SysLogisticsOrderResponse send(LogisticsType type, SysLogistics logistics) throws NoSuitableProxyException {
        if (!filter(type)) {
            throw new NoSuitableProxyException();
        }
        return send(logistics);
    }

    @Override
    public SysRoute routeNotify(String data, LogisticsType type) {
        if (!filter(type)) {
            throw new NoSuitableProxyException();
        }
        log.debug("收到顺丰路由通知：{}", data);
        SFRouteNotifyRequest request = PARSER.parse(data, SFRouteNotifyRequest.class);
        log.debug("顺丰路由通知解析后为：{}", request);
        SFRouteNotifyBody body = request.getBody();
        SysRoute route = BeanUtils.copy(body, SysRoute.class);
        log.debug("将顺丰路由信息[{}]解析为系统路由信息：[{}]", body, route);
        return route;
    }

    @Override
    public SysLogisticsOrderResponse send(SysLogistics logistics) throws NoSuitableProxyException {
        if (sandbox) {
            log.info("当前是沙箱模式，下单为模拟下单");
            SysLogisticsOrderResponse response = new SysLogisticsOrderResponse();
            response.setSuccess(true);
            response.setType(LogisticsType.SF);
            response.setOrderid(logistics.getOrderid());
            response.setMailno("sandbox");
            response.setReturnTrackingNo("sandbox");
            response.setFilterResult("2");
            return response;
        }
        log.debug("系统快递信息为：{}", logistics);
        SFLogisticsRequest request = new SFLogisticsRequest();
        //根据顺丰的接口来定这两个字段
        request.setService("OrderService");


        SFOrder order = BeanUtils.copy(logistics, SFOrder.class);
        SysCargo sysCargo = logistics.getCargo();
        if (sysCargo != null) {
            Cargo cargo = BeanUtils.copy(sysCargo, Cargo.class);
            order.setCargo(cargo);
        }

        request.setBody(new LogisticsBody(order));
        log.debug("要发送的报文POJO为：{}", request);

        return searchOrder(request);
    }

    @Override
    public SysLogisticsOrderResponse searchSend(LogisticsType type, String orderId) throws NoSuitableProxyException {
        if (!filter(type)) {
            throw new NoSuitableProxyException();
        }
        SFOrderSearchRequest request = new SFOrderSearchRequest();
        request.setBody(new SFOrderSearchBody(orderId, "1"));
        request.setService("OrderSearchService");
        log.debug("要发送的报文POJO为：{}", request);

        return searchOrder(request);
    }

    /**
     * 创建或者查询订单（创建订单与查询订单模式一样，所以放到一个方法里）
     *
     * @param request 请求体
     * @return 创建或者查询结果
     */
    private SysLogisticsOrderResponse searchOrder(SFRequest request) {
        SysLogisticsOrderResponse sysLogisticsOrder = new SysLogisticsOrderResponse();
        sysLogisticsOrder.setType(LogisticsType.SF);
        SFLogisticsResponse response = request(url, request, SFLogisticsResponse.class, sysLogisticsOrder);

        if (sysLogisticsOrder.isSuccess()) {
            log.debug("请求成功");
            OrderResponseBody body = response.getBody();
            sysLogisticsOrder.setOrderid(body.getOrderid());
            sysLogisticsOrder.setMailno(body.getMailno());
            sysLogisticsOrder.setReturnTrackingNo(body.getReturnTrackingNo());
            sysLogisticsOrder.setFilterResult(body.getFilterResult());
        }
        return sysLogisticsOrder;
    }

    @Override
    public SysRouteResponse searchRoute(LogisticsType type, String id, boolean isSys) throws
            NoSuitableProxyException {
        if (!filter(type)) {
            throw new NoSuitableProxyException();
        }
        log.debug("查询{}的ID为{}的路由信息，ID是", type, id, isSys ? "系统单号" : "物流单号");
        SFRouteRequest request = new SFRouteRequest();
        request.setService("RouteService");

        SFRouteBody body = new SFRouteBody();
        body.setTrackingNumber(id);
        if (isSys) {
            //根据系统订单号查询
            body.setTrackingType((byte) 2);
        } else {
            //根据物流单号查询
            body.setTrackingType((byte) 1);
        }
        request.setBody(body);
        log.debug("构建的路由请求信息为：{}", request);

        SysRouteResponse route = new SysRouteResponse();
        route.setType(LogisticsType.SF);
        //请求
        SFRouteResponse response = request(url, request, SFRouteResponse.class, route);
        if (route.isSuccess()) {
            log.debug("请求成功");
            SFRouteResponseData data = response.getBody().getData();
            route.setMailno(data.getMailno());
            route.setOrderid(data.getOrderid());
            route.setRoutes(BeanUtils.copy(data.getRoutes(), SysRoute.class));
        }

        return route;
    }

    @Override
    public SysDeliverResponse searchDeliver(LogisticsType type, SysDeliver deliver) throws NoSuitableProxyException {
        //构建请求数据
        SFDeliverRequest request = new SFDeliverRequest();
        request.setService("CalcTimePromiseFreightService");

        SFTmPromiseReParam param = BeanUtils.copy(deliver, SFTmPromiseReParam.class);
        request.setData(param);

        log.info("顺丰时效查询参数为：{}", request);
        //生成用户响应
        SysDeliverResponse response = new SysDeliverResponse();
        response.setType(LogisticsType.SF);
        //发起网络请求
        SFDeliverResponse deliverResponse = request(url, request, SFDeliverResponse.class, response);


        if (response.isSuccess() && deliverResponse.getData() != null && deliverResponse.getData().getResponse() !=
                null && deliverResponse.getData().getResponse().getErrorInfo() == null) {
            log.info("顺丰时效查询成功，响应数据为：{}", deliverResponse);


            List<SysDeliverData> datas = BeanUtils.copy(deliverResponse.getData().getResponse().getList().stream()
                    .map(SFTmFreightOfSgProtRt::getData).collect(Collectors.toList()), SysDeliverData.class);
            response.setDatas(datas);
        } else if (response.isSuccess() && deliverResponse.getData() != null && deliverResponse.getData().getResponse
                () != null && deliverResponse.getData().getResponse().getErrorInfo() != null) {
            //该查询有可能会有该情况，该情况request方法不能处理，只能手动处理
            SFErrorInfo errorInfo = deliverResponse.getData().getResponse().getErrorInfo();
            response.setErrorCode(errorInfo.getErrorCode() + ":" + errorInfo.getErrorDescription());
            //内部请求会认为该请求是成功的，但实际是失败的，需要单独设置
            response.setSuccess(false);
        }

        return response;
    }

    @Override
    public SysNewDeliverResponse searchDeliver(LogisticsType type, SysNewDeliver deliver) throws
            NoSuitableProxyException {
        //构建请求数据
        SFNewDeliverRequest request = new SFNewDeliverRequest();
        request.setService("DeliverTmService");

        SFDeliverTmService tmService = new SFDeliverTmService();
        SFDeliverTmRequestBody param = BeanUtils.copy(deliver, SFDeliverTmRequestBody.class);
        tmService.setBody(param);
        param.setSrcAddress(BeanUtils.copy(deliver.getSrcAddress(), SFAddress.class));
        param.setDestAddress(BeanUtils.copy(deliver.getDestAddress(), SFAddress.class));
        request.setBody(tmService);

        log.info("顺丰时效查询(new)参数为：{}", request);
        //生成用户响应
        SysNewDeliverResponse response = new SysNewDeliverResponse();
        response.setType(LogisticsType.SF);
        //发起网络请求
        SFNewDeliverResponse deliverResponse = request(url, request, SFNewDeliverResponse.class, response);

        if (response.isSuccess()) {
            log.info("顺丰时效查询(new)成功，响应数据为：{}", deliverResponse);

            List<SysNewDeliverData> datas = BeanUtils.copy(deliverResponse.getResponse().getResponse().getDeliverTm()
                    , SysNewDeliverData.class);
            response.setDatas(datas);
        }

        return response;
    }

    /**
     * 往顺丰发请求
     *
     * @param url        请求URL
     * @param data       请求数据
     * @param resultType 请求数据类型
     * @param response   用户的响应（会给用户响应设置请求结果）
     * @param <T>        顺丰响应类型
     * @param <F>        用户响应类型
     * @return 请求顺丰的结果，当不成功时会返回null，是否成功可以根据用户响应中的success判断（先判断用户响应中的success是否
     * <p>
     * 成功，如果不成功就不需要判断顺丰的请求结果了，如果成功了那么需要从顺丰的请求结果中获取需要的数据）
     */
    private <T extends SFResponse, F extends LogisticsResponse> T request(String url, SFRequest data, Class<T>
            resultType, F response) {
        data.setHead(code);
        log.info("要发送的报文POJO为：{}", data);
        //生成要发送的报文
        String xml = PARSER.toXml(data, "Request", false);
        log.info("要发送的报文为：{}", xml);
        try {
            String sendData = "xml=" + URLEncoder.encode(xml, "UTF8") + "&verifyCode=" + URLEncoder.encode(sign(xml),
                    "UTF8");
            log.info("准备往{}发起请求，数据为：{}，结果类型为：{}", url, sendData, resultType);
            String result = CLIENT.executePost(url, sendData, "UTF8", "UTF8",
                    IHttpRequestBase.CONTENT_TYPE_FORM);
            log.info("请求{}的结果是：{}", url, result);
            T sfResponse = PARSER.parse(result, resultType);
            log.info("解析[{}]的结果为：{}", result, sfResponse);
            if (sfResponse == null || "ERR".equals(sfResponse.getHead())) {
                response.setSuccess(false);
                response.setErrorCode(sfResponse == null ? "未知原因" : (sfResponse.getCode() + ":" + sfResponse
                        .getMsg()));
                log.warn("接口请求失败，失败原因：", response.getErrorCode());
            } else {
                response.setSuccess(true);
            }
            return sfResponse;
        } catch (Throwable e) {
            log.error("请求{}异常，请求数据为：{}，请求结果类型为：{}，用户response为：{}", url, data, resultType, response);
            response.setSuccess(false);
            response.setE(e);
            response.setErrorCode("系统异常");
            return null;
        }
    }

    /**
     * 生成签名
     *
     * @param xml 要发送的xml报文
     * @return 签名
     */
    private String sign(String xml) {
        String sign = xml + checkword;
        return new String(BASE64.encrypt(MD5.encrypt16(sign.getBytes())));
    }
}
